// Package clock is a low consumption, low latency support for frequent updates of large capacity timing manager：
//	1、能够添加一次性、重复性任务，并能在其执行前撤销或频繁更改。
//	2、支持同一时间点，多个任务提醒。
//	3、适用于中等密度，大跨度的单次、多次定时任务。
//	4、支持10万次/秒的定时任务执行、提醒、撤销或添加操作，平均延迟10微秒内
//	5、支持注册任务的函数调用，及事件通知。
// 基本处理逻辑：
//	1、重复性任务，流程是：
//		a、注册重复任务
//		b、时间抵达时，控制器调用注册函数，并发送通知
//		c、如果次数达到限制，则撤销；否则，控制器更新该任务的下次执行时间点
//		d、控制器等待下一个最近需要执行的任务
//	2、一次性任务，可以是服务运行时，当前时间点之后的任意事件，流程是：
//		a、注册一次性任务
//		b、时间抵达时，控制器调用注册函数，并发送通知
//		c、控制器释放该任务
//		d、控制器等待下一个最近需要执行的任务
// 使用方式，参见示例代码。
package clock

import (
	"fmt"
	"github.com/jacktea/z-cron/cron"
	"github.com/jacktea/z-cron/rbtree"
	"math"
	"sync"
	"sync/atomic"
	"time"
)

const _UNTOUCHED = time.Duration(math.MaxInt64)

var (
	defaultClock *Clock
	oncedo       sync.Once
)

//Default return singal default clock
func Default() *Clock {
	oncedo.Do(initClock)
	return defaultClock
}
func initClock() {
	defaultClock = NewClock()
}

// Clock is jobs schedule
type Clock struct {
	seq         uint64
	jobQueue    *rbtree.Rbtree //inner memory storage
	count       uint64         //已执行次数，不得大于times
	waitJobsNum uint64         //num of jobs which wait for action
	pauseChan   chan struct{}
	resumeChan  chan struct{}
	exitChan    chan struct{}
}

var singal = struct{}{}

//NewClock Create a task queue controller
func NewClock() *Clock {
	c := &Clock{
		jobQueue:   rbtree.New(),
		pauseChan:  make(chan struct{}, 0),
		resumeChan: make(chan struct{}, 0),
		exitChan:   make(chan struct{}, 0),
	}

	c.start()

	return c
}
func (jl *Clock) start() {
	now := time.Now()

	_, inserted := jl.addJob(now, time.Duration(math.MaxInt64), 1, emptyJobFunc, emptyJobFunc) // insert untouchedJob
	if !inserted {
		panic("[clock] internal error.Reason cannot insert job.")
	}
	//开启守护协程
	go jl.schedule()
	jl.resume()
}

func (jl *Clock) pause() {
	jl.pauseChan <- singal
}
func (jl *Clock) resume() {
	jl.resumeChan <- singal
}
func (jl *Clock) exit() {
	jl.exitChan <- singal
}

func (jl *Clock) immediate() {
	//fmt.Println("----- immediate")
	for {
		if item := jl.jobQueue.Min(); item != nil {
			atomic.AddUint64(&jl.count, 1)

			job := item.(*jobItem)
			job.action(false)

			jl.removeJob(job)

		} else {
			break
		}
	}
}

func (jl *Clock) insertJob(job *jobItem) {
	//fmt.Println("insert 1")
	jl.pause()
	defer jl.resume()
	//fmt.Println("insert 2")
	jl.jobQueue.Insert(job)
	jl.waitJobsNum++
	//fmt.Println("insert 3")
}

func (jl *Clock) minJob() *jobItem {
	job, ok := jl.jobQueue.Min().(*jobItem)
	if ok {
		return job
	} else {
		return nil
	}
}

//func (jl *Clock) process(job *jobItem) {
//	if job.exec() {
//		//jl.lock.Lock()
//		jl.jobQueue.Delete(job)
//		jl.jobQueue.Insert(job)
//		//jl.lock.Unlock()
//	} else {
//		//jl.lock.Lock()
//		jl.removeJob(job)
//		//jl.lock.Unlock()
//	}
//}

func (jl *Clock) schedule() {
	var (
		timeout time.Duration
		job     *jobItem
		timer   = newSafeTimer(_UNTOUCHED)
	)
	defer timer.Stop()
Pause:
	//fmt.Println("do pause")
	<-jl.resumeChan
	for {
		//job, _ = jl.jobQueue.Min().(*jobItem) //ignore ok-assert
		//start := time.Now().Unix()
		job = jl.minJob()
		//fmt.Println("get job", job.id, job.actionTime.Format("2006-01-02 15:04:05"))
		timeout = job.actionTime.Sub(time.Now())
		timer.SafeReset(timeout)
		select {
		case <-timer.C:
			//star1 := time.Now().UnixNano()
			timer.SCR()

			atomic.AddUint64(&jl.count, 1)
			jl.removeJob1(job)

			job.exec()
			//fmt.Println("complete", time.Now().Format("2006-01-02 15:04:05"))

			//jl.process(job)

			//job.action(true)
			//nextTime := job.GetActionTime()
			//// 最大执行次数为零或执行次数小于最大执行次数，并且下次执行时间不为零
			//if (job.actionMax == 0 || job.actionMax > job.actionCount) && !nextTime.IsZero(){
			//	jl.jobQueue.Delete(job)
			//	//job.actionTime = job.actionTime.Add(job.intervalTime)
			//	job.actionTime = nextTime
			//	jl.jobQueue.Insert(job)
			//} else {
			//	jl.removeJob(job)
			//}
			//fmt.Println("rel", time.Now().UnixNano() - star1)
			//fmt.Println("exec", time.Now().Unix() - start)
		case <-jl.pauseChan:
			//fmt.Println("pause", time.Now().Unix() - start)
			goto Pause
		case <-jl.exitChan:
			//fmt.Println("exit", time.Now().Unix() - start)
			goto Exit
		}
	}
Exit:
}

// UpdateJobTimeout update a timed task with time duration after now
//	@job:			job identifier
//	@actionTime:	new job schedule time,must be greater than 0
func (jl *Clock) UpdateJobTimeout(job Job, timeout time.Duration) (updated bool) {
	if job == nil || timeout.Nanoseconds() <= 0 || !job.isAvailable() {
		return false
	}

	spec := fmt.Sprintf("@every %s", timeout.Round(time.Second))
	return jl.UpdateCronJob(job, spec)

	//now := time.Now()
	//
	//item, ok := job.(*jobItem)
	//if !ok {
	//	return false
	//}
	//jl.pause()
	//defer jl.resume()
	//
	//// update jobitem in job queue
	//jl.jobQueue.Delete(item)
	//item.actionTime = now.Add(timeout)
	//jl.jobQueue.Insert(item)
	//
	//updated = true
	//return
}

func (jl *Clock) UpdateJobDeadTime(job Job, actionTime time.Time) (updated bool) {

	return jl.UpdateJobTimeout(job, actionTime.Sub(time.Now()))

	//now := time.Now()
	//if job == nil || now.After(actionTime) || !job.isAvailable() {
	//	return false
	//}
	//
	//item, ok := job.(*jobItem)
	//if !ok {
	//	return false
	//}
	//jl.pause()
	//defer jl.resume()
	//
	//// update jobitem in job queue
	//jl.jobQueue.Delete(item)
	//item.actionTime = actionTime
	//jl.jobQueue.Insert(item)
	//
	//updated = true
	//return
}

func (jl *Clock) UpdateCronJob(job Job, spec string) (updated bool) {
	if job == nil || spec == "" || !job.isAvailable() {
		return false
	}
	parser := cron.NewParser(cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor)
	s, err := parser.Parse(spec)
	if err != nil {
		return false
	}

	item, ok := job.(*jobItem)
	if !ok {
		return false
	}
	jl.pause()
	defer jl.resume()

	// update jobitem in job queue
	jl.jobQueue.Delete(item)
	item.schedule = s
	item.actionTime = s.Next(time.Now())
	jl.jobQueue.Insert(item)

	updated = true
	return
}

func (jl *Clock) AddCronJob(spec string, jobFunc JobFunc, delFunc JobFunc) (jobScheduled Job, inserted bool) {
	if jobFunc == nil || spec == "" {
		return
	}
	now := time.Now()
	jl.pause()
	defer jl.resume()
	//jl.pause()
	//start := time.Now().UnixNano()
	jobScheduled, inserted = jl.addCronJob(now, spec, 0, jobFunc, delFunc)
	//fmt.Println("inner 1", time.Now().UnixNano() - start)
	//jl.resume()
	return
}

// AddJobWithInterval insert a timed task with time duration after now
// 	@actionTime:	Duration after now
//	@jobFunc:		Callback function,not nil
//	return
// 	@jobScheduled:	A reference to a task that has been scheduled.
func (jl *Clock) AddJobWithInterval(actionInterval time.Duration, jobFunc JobFunc, delFunc JobFunc) (jobScheduled Job, inserted bool) {
	if jobFunc == nil || actionInterval.Nanoseconds() <= 0 {
		return
	}
	now := time.Now()

	jl.pause()
	defer jl.resume()
	//jl.pause()
	jobScheduled, inserted = jl.addJob(now, actionInterval, 1, jobFunc, delFunc)
	//jl.resume()

	return
}

// AddJobWithDeadtime insert a timed task with time point after now
//	@actionTime:	Execution start time. must after now
//	@jobFunc:		Callback function,not nil
//	return
// 	@jobScheduled	:	A reference to a task that has been scheduled.
//	@inserted		:	return false ,if actionTime before time.Now or jobFunc is nil
func (jl *Clock) AddJobWithDeadTime(actionTime time.Time, jobFunc JobFunc, delFunc JobFunc) (jobScheduled Job, inserted bool) {
	actionInterval := actionTime.Sub(time.Now())
	if jobFunc == nil || actionInterval.Nanoseconds() <= 0 {
		return
	}
	now := time.Now()

	jl.pause()
	defer jl.resume()
	//jl.pause()
	jobScheduled, inserted = jl.addJob(now, actionInterval, 1, jobFunc, delFunc)
	//jl.resume()

	return
}

// AddJobRepeat add a repeat task with interval duration
//	@interval:		The interval between two actions of the job
//	@actionMax:		The number of job execution
//	@jobFunc:		Callback function,not nil
//	return
// 	@jobScheduled	:	A reference to a task that has been scheduled.
//	@inserted		:	return false ,if interval is not Positiveor jobFunc is nil
//Note：
// when jobTimes==0,the job will be executed without limitation。If you no longer use, be sure to call the DelJob method to release
func (jl *Clock) AddJobRepeat(interval time.Duration, actionMax uint64, jobFunc JobFunc, delFunc JobFunc) (jobScheduled Job, inserted bool) {
	if jobFunc == nil || interval.Nanoseconds() <= 0 {
		return
	}
	now := time.Now()

	jl.pause()
	defer jl.resume()
	//jl.pause()
	jobScheduled, inserted = jl.addJob(now, interval, actionMax, jobFunc, delFunc)
	//jl.resume()

	return
}

func (jl *Clock) addJob(createTime time.Time, actionInterval time.Duration, actionMax uint64, jobFunc JobFunc, delFunc JobFunc) (job *jobItem, inserted bool) {
	//parser := cron.NewParser(cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor)
	//s, err := parser.Parse(fmt.Sprintf("@every %s", actionInterval))
	//if err != nil {
	//	return nil, false
	//}
	//jl.seq++
	//jl.waitJobsNum++
	//job = &jobItem{
	//	id:           jl.seq,
	//	actionMax:    actionMax,
	//	createTime:   createTime,
	//	actionTime:   createTime.Add(actionInterval),
	//	//intervalTime: actionInterval,
	//	schedule:	  s,
	//	msgChan:      make(chan Job, 10),
	//	fn:           jobFunc,
	//	clock:        jl,
	//}
	//jl.jobQueue.Insert(job)
	//inserted = true
	//
	//return
	//fmt.Println("-------" ,
	//	createTime.Add(actionInterval).Format("2006-01-02 15:04:05"),
	//	actionInterval,
	//	actionInterval.Seconds(),
	//	actionInterval.Round(time.Second))
	spec := fmt.Sprintf("@every %s", actionInterval.Round(time.Second))
	return jl.addCronJob(createTime, spec, actionMax, jobFunc, delFunc)
}

func (jl *Clock) addCronJob(createTime time.Time, spec string, actionMax uint64, jobFunc JobFunc, delFunc JobFunc) (job *jobItem, inserted bool) {
	//jl.pause()
	//defer jl.resume()
	parser := cron.NewParser(cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor)
	s, err := parser.Parse(spec)
	if err != nil {
		return nil, false
	}
	jl.seq++
	jl.waitJobsNum++
	job = &jobItem{
		id:         jl.seq,
		actionMax:  actionMax,
		createTime: createTime,
		actionTime: s.Next(createTime),
		schedule:   s,
		msgChan:    make(chan Job, 10),
		fn:         jobFunc,
		delFn:      delFunc,
		clock:      jl,
	}
	//fmt.Println("-------" , s.Next(createTime).Format("2006-01-02 15:04:05"))
	jl.jobQueue.Insert(job)
	inserted = true

	return

}

func (jl *Clock) removeJob1(item *jobItem) {
	if jl.jobQueue.Delete(item) != nil {
		jl.waitJobsNum--
	}
	//job := jl.jobQueue.Min().(*jobItem)
	//fmt.Println("remove get", job.id, job.actionTime.Format("2006-01-02 15:04:05"))
}

func (jl *Clock) removeJob(item *jobItem) {
	//jl.lock.Lock()

	if jl.jobQueue.Delete(item) != nil {
		jl.waitJobsNum--

		//job.Cancel --> rmJob -->removeJob; schedule -->removeJob
		//it is call repeatly when Job.Cancel
		//fmt.Println("---clock remove job")
		if atomic.CompareAndSwapInt32(&item.cancelFlag, 0, 1) {
			item.innerCancel()
		}

	}
	//jl.lock.Unlock()

	//job := jl.jobQueue.Min().(*jobItem)
	//fmt.Println("remove get", job.id, job.actionTime.Format("2006-01-02 15:04:05"))
}

func (jl *Clock) rmJob(job *jobItem) {
	jl.pause()
	defer jl.resume()

	jl.removeJob(job)
	return
}

// Count 已经执行的任务数。对于重复任务，会计算多次
func (jl *Clock) Count() uint64 {
	return atomic.LoadUint64(&jl.count)
}

//重置Clock的内部状态
func (jl *Clock) Reset() *Clock {
	jl.exit()
	jl.count = 0

	jl.cleanJobs()
	jl.start()
	return jl
}

func (jl *Clock) cleanJobs() {
	item := jl.jobQueue.Min()
	for item != nil {
		job, ok := item.(*jobItem)
		if ok {
			jl.removeJob(job)
		}
		item = jl.jobQueue.Min()
	}
}

//WaitJobs get how much jobs waiting for call
func (jl *Clock) WaitJobs() uint64 {
	jobs := atomic.LoadUint64(&jl.waitJobsNum) - 1
	return jobs
}

//Stop stop clock , and cancel all waiting jobs
func (jl *Clock) Stop() {
	jl.exit()

	jl.cleanJobs()
}

//StopGracefull stop clock ,and do once every waiting job including Once\Reapeat
//Note:对于任务队列中，即使安排执行多次或者不限次数的，也仅仅执行一次。
func (jl *Clock) StopGraceful() {
	jl.exit()

	jl.immediate()
}
